package net.towee.client.com.lobby.presenter;

import java.util.Arrays;
import java.util.List;

import net.towee.client.com.app.Presenter;
import net.towee.client.com.lobby.Lobby;
import net.towee.client.com.lobby.LobbyStep;
import net.towee.client.managers.AccountsManager;
import net.towee.client.managers.ContactsManager;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.ContactsGroup;
import net.towee.model.utils.Pair;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
@SuppressWarnings("rawtypes")
public class LobbyPeoplePresenter implements Presenter, LobbyStep, ChangeHandler, OpenHandler, ClickHandler {

	public interface Display extends IsWidget {
		HasChangeHandlers getSearchBox();

		String getSearchString();

		/**
		 * Add founded user to the search result. Method must return container
		 * of events for adding account to some group of contacts.
		 * 
		 * Fire the OpenEvent handler with pair of contacts group and account
		 * identity to put user to group.
		 * 
		 * @param account
		 * @return
		 */
		HasOpenHandlers<Pair<ContactsGroup, AccountIdentity>> addFoundUser(AccountIdentity account);

		/**
		 * Add account to given group. Must return container of ClickEvent for
		 * removing account from group.
		 * 
		 * Implementation must set in layout data Pair<ContactsGroup,
		 * AccountIdentity>
		 * 
		 * @param idnt
		 * @param group
		 * @return
		 */
		HasClickHandlers addAccountToContactGroup(AccountIdentity idnt, ContactsGroup group);

		/**
		 * Add group of contacts to the interface. This method must return
		 * container of ClickEvent for removing this group.
		 * 
		 * Implementation must set in layout data given group.
		 * 
		 * @param group
		 * @return
		 */
		HasClickHandlers addContactsGroup(ContactsGroup group);

		/**
		 * Fire the OpenEvent for creating new ContactsGroup. After creating
		 * Presenter invoke addContactsGroup method.
		 * 
		 * @return
		 */
		HasOpenHandlers<String> getAddContactGroupHandlers();

		/**
		 * Remove given group from display. It invoked after successfully
		 * deleting group.
		 * 
		 * @param g
		 */
		void removeContactsGroup(ContactsGroup g);

		/**
		 * Remove given account from given contacts group. It invoked after
		 * successfully removing the account on the server
		 * 
		 * @param second
		 * @param first
		 */
		void removeAccountFromGroup(AccountIdentity second, ContactsGroup first);
		
		/**
		 * Button for showing more search result
		 * @return
		 */
		HasClickHandlers getMoreResultButton();

		/**
		 * Invoked before new searching
		 */
		void clearOldSearchResult();
	}

	// Injected
	private final Display display;
	private final ContactsManager contacts;
	private final AccountsManager accounts;
	
	// Search indexes
	private final int perPage = 30;
	private int offset = 0;

	@Inject
	public LobbyPeoplePresenter(Lobby lobby, Display display, ContactsManager contacts,
			AccountsManager accounts) {
		this.display = display;
		this.contacts = contacts;
		this.accounts = accounts;

		display.getSearchBox().addChangeHandler(this);
		display.getAddContactGroupHandlers().addOpenHandler(this);
		display.getMoreResultButton().addClickHandler(this);
	}

	@Override
	public boolean validateStep() {
		return true;
	}

	@Override
	public void go(HasWidgets container) {
		container.clear();
		container.add(display.asWidget());
	}

	/**
	 * Handler of ContactsGroup deleting
	 */
	private final ClickHandler removeGroupHandler = new ClickHandler() {
		@Override
		public void onClick(ClickEvent event) {
			// Get group
			final ContactsGroup g = (ContactsGroup) ((Widget) event.getSource()).getLayoutData();

			// Delete group
			contacts.deleteContactsGroup(g.getId(), new AsyncCallback<Boolean>() {

				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(Boolean result) {
					display.removeContactsGroup(g);
				}
			});
		}
	};

	/**
	 * Handler of deleting account from group
	 */
	private final ClickHandler removeAccountHandler = new ClickHandler() {
		@Override
		public void onClick(ClickEvent event) {
			// Get account
			final Pair<ContactsGroup, AccountIdentity> pair = (Pair<ContactsGroup, AccountIdentity>) ((Widget) event
					.getSource()).getLayoutData();

			// Remove on server
			contacts.removeContactFromGroup(pair.getFirst().getId(),
					Arrays.asList(pair.getSecond().getId()), new AsyncCallback<Boolean>() {
						@Override
						public void onFailure(Throwable caught) {
						}

						@Override
						public void onSuccess(Boolean result) {
							display.removeAccountFromGroup(pair.getSecond(), pair.getFirst());
						}
					});
		}
	};

	@Override
	public void onOpen(OpenEvent event) {
		Object source = event.getSource();

		// On add contacts group
		if (source.equals(display.getAddContactGroupHandlers())) {
			// Check group name on empty
			Preconditions.checkArgument(!Strings.isNullOrEmpty((String) event.getTarget()),
					"null group name");

			// Create group
			contacts.createContactsGroup((String) event.getTarget(), new AsyncCallback<ContactsGroup>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(ContactsGroup result) {
					display.addContactsGroup(result).addClickHandler(removeGroupHandler);
				}

			});
		}
		// On add account to group
		else {
			// Get target
			final Pair<ContactsGroup, AccountIdentity> target = (Pair<ContactsGroup, AccountIdentity>) event
					.getTarget();

			// Add user
			contacts.addContactsToGroup(target.getFirst().getId(), Arrays.asList(target.getSecond()),
					new AsyncCallback<Boolean>() {
						@Override
						public void onFailure(Throwable caught) {
						}

						@Override
						public void onSuccess(Boolean result) {
							display.addAccountToContactGroup(target.getSecond(), target.getFirst())
									.addClickHandler(removeAccountHandler);
						}
					});
		}
	}

	/**
	 * Invoked on change search input box
	 */
	@Override
	public void onChange(ChangeEvent event) {
		if (!display.getSearchString().isEmpty()) {
			// Set zero offset
			offset = 0;
			
			// Clear prev result
			display.clearOldSearchResult();
			
			// Start finding of users
			findeNextUsers();
		}
	}
	
	/**
	 * Click on More Result button
	 */
	@Override
	public void onClick(ClickEvent event) {
		// Find next users
		findeNextUsers();
	}
	
	/**
	 * Find next users
	 */
	private void findeNextUsers(){
		accounts.findAccount(display.getSearchString(), offset, perPage, new AsyncCallback<List<AccountIdentity>>() {
			@Override
			public void onFailure(Throwable caught) {
			}

			@Override
			public void onSuccess(List<AccountIdentity> result) {
				offset += perPage;
				
				for (AccountIdentity idnt : result) {
					display.addFoundUser(idnt).addOpenHandler( LobbyPeoplePresenter.this );
				}
			}
		});
	}
}
